using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;

namespace SunrayEngine.Maps
{
    /// <summary>
    /// A SunRay Engine map
    /// </summary>
    public class Map
    {
        #region fields
        private MapGrid grid;//The map grid that draws and handels the subparts of the map.
        //This way we can have per pixel editing and still have the same performance as a normal tile map.
        private QuadTree quadTree; //Used for fast scanning, collision, finding, etc.
        private int width; //The map width
        private int height; //The map height
        #endregion

        #region properties
        /// <summary>
        /// The Map tiles.
        /// </summary>
        public MapTile[,] Tiles { get { return grid.Tiles; } }

        /// <summary>
        /// Reference to the games graphicsDevice.
        /// </summary>
        public static GraphicsDevice Device { get; set; }

        /// <summary>
        /// The maps identifier
        /// </summary>
        public string Name { get; protected set; }

        /// <summary>
        /// The width of the map
        /// </summary>
        public int Width { get { return width; } }

        /// <summary>
        /// The height of the map
        /// </summary>
        public int Height { get { return height; } }
        #endregion

        #region initialization
        public Map(int width, int height)
        {
            this.width = width;
            this.height = height;
            quadTree = new QuadTree(width, height);
        }

        public void Initialize(Rectangle mapDisplayBounds)
        {
            grid = new MapGrid(0, 0, width, height, Device, mapDisplayBounds);
        }
        #endregion

        #region methods
        /// <summary>
        /// Draws the map
        /// </summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            grid.Draw(spriteBatch);
        }

        /// <summary>
        /// Set all the pixels information
        /// </summary>
        /// <param name="info"></param>
        public void SetTiles(MapTile[,] tiles)
        {
            grid.Tiles = tiles;
        }

        public void Save(string path)
        {
            if (!File.Exists(path))
            {
                FileStream stream = File.Create(path);
                stream.Close();
                stream.Dispose();
            }

            using (StreamWriter w = new StreamWriter(path))
            {
                //Write map bounds and tile bounds.
                w.Write(string.Format("{0}|{1}:{2}|{3}", width, height, grid.Tiles[0, 0].Pixels.GetLength(0), grid.Tiles[0, 0].Pixels.GetLength(1)));

                //Iteriate all tiles.
                for(int x = 0; x < width; x++)
                    for(int y  = 0; y < height; y++)
                    {
                        //Iteriate all pixels for each tile.
                        for (int i = 0; i < grid.Tiles[x, y].Pixels.GetLength(0); i++)
                        {
                            w.Write("\n");
                            for (int j = 0; j < grid.Tiles[x, y].Pixels.GetLength(1); j++)
                            {
                                PixelInfo info = grid.Tiles[x,y].Pixels[i,j];
                                w.Write(string.Format("{0}|{1}|{2}|{3}-{4};", 
                                    info.Color.A, info.Color.R, info.Color.G, info.Color.B, info.WalkAbel ? 't' : 'f'));
                            }
                        }
                    }

                w.Close();
            }
        }

        public void Save(string path, Func<bool> onOverwrite)
        {
            if (File.Exists(path))
                if (!onOverwrite())
                    return;

                Save(path);
        }

        public static Map Load(string path, Rectangle mapDisplayBounds)
        {
            if (!File.Exists(path))
                throw new IOException("The file don't exists");

            Map map;
            using (StreamReader r = new StreamReader(path))
            {
                //Get the map and tile bounds.
                string[] boundsData =  r.ReadLine().Split(':');
                int mw = int.Parse(boundsData[0].Split('|')[0]);
                int mh = int.Parse(boundsData[0].Split('|')[1]);
                int tw = int.Parse(boundsData[1].Split('|')[0]);
                int th = int.Parse(boundsData[1].Split('|')[1]);

                map = new Map(mw, mh);
                map.Initialize(mapDisplayBounds);

                for(int x = 0; x < mw; x++)
                    for (int y = 0; y < mh; y++)
                        for(int i = 0; i < tw; i++)
                        {
                            string[] data = r.ReadLine().Split(';');

                            for (int j = 0; j < th; j++)
                            {
                                string[] p = data[j].Split('-');
                                string[] colData = p[0].Split('|');

                                int alpha = int.Parse(colData[0]);
                                int red = int.Parse(colData[1]);
                                int green = int.Parse(colData[2]);
                                int blue = int.Parse(colData[3]);

                                Color c = new Color(red, green, blue, alpha);

                                map.grid.Tiles[x, y].Pixels[i, j].Color = c;
                                map.grid.Tiles[x, y].Pixels[i, j].WalkAbel = p[1][0] == 't';
                            }

                            map.grid.Tiles[x, y].WalkabelToTexture();
                            map.grid.Tiles[x, y].PixelsToTexture();
                        }

                r.Close();
            }

            return map;
        }

        public static Map Load(string path, Rectangle mapDisplayBounds, GraphicsDevice device)
        {
            Map.Device = device;
            return Load(path, mapDisplayBounds);
        }
        #endregion
    }
}
